package com.birdy.jwt.service.impl;

import com.birdy.jwt.config.JWTConfigProperties;
import com.birdy.jwt.exception.InvalidTokenException;
import com.birdy.jwt.exception.TokenExpiredException;
import com.birdy.jwt.service.ITokenService;
import com.birdy.jwt.vo.Pair;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author birdy
 * 此时的组件中的代码需要被其他的模块去引用，所以未必会与扫描包相同
 */
public class TokenServiceImpl implements ITokenService {
    /**
     *  SpringBoot容器启动时会自动提供Jacks实例
     *  Jackson的数据处理类对象
     */
    private final ObjectMapper objectMapper = new ObjectMapper();
    /**
     *  获取JWT的相关配置属性
     */
    @Autowired
    private JWTConfigProperties jwtConfigProperties;
    /**
     * 通过SpEL进行配置注入
     */
    @Value("${spring.application.name}")
    private String applicationName;
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    @Override
    public SecretKey generalKey() {
        byte [] encodeKey = Base64.decodeBase64(Base64.encodeBase64(this.jwtConfigProperties.getSecret().getBytes()));
        // 获取加密KEY
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
    }

    @Override
    public String createToken(String id) {
        // 使用JWT数据结构进行开发，目的之一就是不需要进行JWT数据的分布式存储，所以所谓的缓存组件、数据库都用不到
        // 所有的Token都存在有保存时效的问题，所以就需要通过当前时间来进行计算
        // 获取当前的日期时间
        Date nowDate = new Date();
        // 证书过期时间
        Date expireDate = new Date(nowDate.getTime() + this.jwtConfigProperties.getExpire() * 1000);
        // 保存头信息
        Map<String, Object> headers = new HashMap<>(2);
        // 后续由于很多的模块都会引用此组件，所以为了后续的安全，最佳的做法就是设置一个模块名称的信息
        headers.put("module", this.applicationName);
        // 进行JWTBuilder对象实例化
        JwtBuilder builder = Jwts.builder().setHeader(headers).setId(id).setIssuedAt(nowDate)
                .setIssuer(this.jwtConfigProperties.getIssuer())
                .signWith(this.signatureAlgorithm, this.generalKey())
                .setExpiration(expireDate);
        // 创建Token
        return builder.compact();
    }

    @Override
    public String createToken(String id, Pair ... pairs) {
        // 使用JWT数据结构进行开发，目的之一就是不需要进行JWT数据的分布式存储，所以所谓的缓存组件、数据库都用不到
        // 所有的Token都存在有保存时效的问题，所以就需要通过当前时间来进行计算
        // 获取当前的日期时间
        Date nowDate = new Date();
        // 证书过期时间
        Date expireDate = new Date(nowDate.getTime() + this.jwtConfigProperties.getExpire() * 1000);
        // 保存头信息
        Map<String, Object> headers = new HashMap<>(2);
        // 后续由于很多的模块都会引用此组件，所以为了后续的安全，最佳的做法就是设置一个模块名称的信息
        headers.put("module", this.applicationName);
        Map<String, Object> claims = new HashMap<>(2);
        for (Pair pair : pairs) {
            claims.put(pair.getKey(), pair.getValue());
        }
        // 进行JWTBuilder对象实例化
        JwtBuilder builder = Jwts.builder().setHeader(headers).setId(id).setIssuedAt(nowDate)
                .setClaims(claims)
                .setIssuer(this.jwtConfigProperties.getIssuer())
                .signWith(this.signatureAlgorithm, this.generalKey())
                .setExpiration(expireDate);
        // 创建Token
        return builder.compact();
    }

    @Override
    public String getOpenid(String token) throws InvalidTokenException, JwtException {
        Jws<Claims> claimsJws = parseToken(token);
        String openid = (String) claimsJws.getBody().get("openid");
        if (openid == null) throw new InvalidTokenException();
        return openid;
    }

    @Override
    public Jws<Claims> parseToken(String token) throws JwtException {
        // 只有正确的时候再进行Token解析
        if (this.verifyToken(token)) {
            return Jwts.parser().setSigningKey(this.generalKey()).parseClaimsJws(token);
        }
        // 解析失败返回null
        return null;
    }

    @Override
    public boolean verifyToken(String token) {
        try {
            Jwts.parser().setSigningKey(this.generalKey()).parseClaimsJws(token).getBody();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public String refreshToken(String token) throws TokenExpiredException {
        if (this.verifyToken(token)) {
            // 解析Token数据
            Jws<Claims> jws = this.parseToken(token);
            return this.createToken(jws.getBody().getId(), new Pair("openid", jws.getBody().get("openid")));
        }
        throw new TokenExpiredException();
    }


}

